Expand description
Easily retry futures.
Example usage
// some async function that can fail
async fn read_file(path: &str) -> Result<String, std::io::Error> {
// ...
}
let contents = tryhard::retry_fn(|| read_file("Cargo.toml"))
// retry at most 10 times
.retries(10)
.await?;
assert!(contents.contains("tryhard"));
You can also customize which backoff strategy to use and what the max retry delay should be:
use std::time::Duration;
let contents = tryhard::retry_fn(|| read_file("Cargo.toml"))
.retries(10)
.exponential_backoff(Duration::from_millis(10))
.max_delay(Duration::from_secs(1))
.await?;
assert!(contents.contains("tryhard"));
Retrying several futures in the same way
Using RetryFutureConfig
you’re able to retry several futures in the same way:
use tryhard::RetryFutureConfig;
let config = RetryFutureConfig::new(10)
.exponential_backoff(Duration::from_millis(10))
.max_delay(Duration::from_secs(3));
tryhard::retry_fn(|| read_file("Cargo.toml"))
.with_config(config)
.await?;
// retry another future in the same way
tryhard::retry_fn(|| read_file("src/lib.rs"))
.with_config(config)
.await?;
How many times will my future run?
The future is always run at least once, so if you do .retries(0)
your future will run once.
If you do .retries(10)
and your future always fails it’ll run 11 times.
Why do you require a closure?
Due to how futures work in Rust you’re not able to retry a bare F where F: Future
. A future
can possibly fail at any point in its execution and might be in an inconsistent state after the
failing. Therefore retrying requires making a fresh future for each attempt.
This means you cannot move values into the closure that produces the futures. You’ll have to clone instead:
async fn future_with_owned_data(data: Vec<u8>) -> Result<(), std::io::Error> {
// ...
}
let data: Vec<u8> = vec![1, 2, 3];
tryhard::retry_fn(|| {
// We need to clone `data` here. Otherwise we would have to move `data` into the closure.
// `move` closures can only be called once (they only implement `FnOnce`)
// and therefore cannot be used to create more than one future.
let data = data.clone();
async {
future_with_owned_data(data).await
}
}).retries(10).await?;
Be careful what you retry
This library is meant to make it straight forward to retry simple futures, such as sending a single request to some service that occationally fails. If you have some complex operation that consists of multiple futures each of which can fail, this library might be not appropriate. You risk repeating the same operation more than once because some later operation keeps failing.
Tokio only for now
This library currently expects to be used from within a tokio runtime. That is because it makes use of async timers. Feel free to open an issue if you need support for other runtimes.
Modules
- The types of backoff strategies that are supported
Structs
- A sentinel value that represents doing nothing in between retries.
- A type that produces retryable futures.
- A retryable future.
- Configuration describing how to retry a future.
Enums
- What to do when a future returns an error. Used with
RetryFuture::custom
.
Traits
- Trait allowing you to run some future when a retry occurs. Could for example to be used for logging or other kinds of telemetry.
Functions
- Create a
RetryFn
which produces retryable futures.